{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 对用户进行聚类\n",
    "\n",
    "数据来源于Kaggle竞赛：Event Recommendation Engine Challenge，根据\n",
    "events they’ve responded to in the past\n",
    "user demographic information\n",
    "what events they’ve seen and clicked on in our app\n",
    "用户对某个事件是否感兴趣\n",
    "\n",
    "竞赛官网：\n",
    "https://www.kaggle.com/c/event-recommendation-engine-challenge/data\n",
    "\n",
    "由于用户众多（3w+），可以对用户进行聚类\n",
    "事件描述信息在users.csv文件：共110维特征\n",
    "user_id\n",
    "locale：地区，语言\n",
    "birthyear：出身年\n",
    "gender：性别\n",
    "joinedAt：用户加入APP的时间，ISO-8601 UTC time\n",
    "location：地点\n",
    "timezone：时区\n",
    "\n",
    "作业要求：\n",
    "根据用户的属性进行聚类（KMeans聚类）\n",
    "尝试K=20， 40， 80，并计算各自CH_scores。\n",
    "\n",
    "提示：由于样本数目较多，建议使用MiniBatchKMeans。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "## 导入工具包\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "from sklearn import metrics\n",
    "import time\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style>\n",
       "    .dataframe thead tr:only-child th {\n",
       "        text-align: right;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: left;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>user_id</th>\n",
       "      <th>locale</th>\n",
       "      <th>birthyear</th>\n",
       "      <th>gender</th>\n",
       "      <th>joinedAt</th>\n",
       "      <th>location</th>\n",
       "      <th>timezone</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>3197468391</td>\n",
       "      <td>id_ID</td>\n",
       "      <td>1993</td>\n",
       "      <td>male</td>\n",
       "      <td>2012-10-02T06:40:55.524Z</td>\n",
       "      <td>Medan  Indonesia</td>\n",
       "      <td>480.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>3537982273</td>\n",
       "      <td>id_ID</td>\n",
       "      <td>1992</td>\n",
       "      <td>male</td>\n",
       "      <td>2012-09-29T18:03:12.111Z</td>\n",
       "      <td>Medan  Indonesia</td>\n",
       "      <td>420.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>823183725</td>\n",
       "      <td>en_US</td>\n",
       "      <td>1975</td>\n",
       "      <td>male</td>\n",
       "      <td>2012-10-06T03:14:07.149Z</td>\n",
       "      <td>Stratford  Ontario</td>\n",
       "      <td>-240.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1872223848</td>\n",
       "      <td>en_US</td>\n",
       "      <td>1991</td>\n",
       "      <td>female</td>\n",
       "      <td>2012-11-04T08:59:43.783Z</td>\n",
       "      <td>Tehran  Iran</td>\n",
       "      <td>210.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>3429017717</td>\n",
       "      <td>id_ID</td>\n",
       "      <td>1995</td>\n",
       "      <td>female</td>\n",
       "      <td>2012-09-10T16:06:53.132Z</td>\n",
       "      <td>NaN</td>\n",
       "      <td>420.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      user_id locale birthyear  gender                  joinedAt  \\\n",
       "0  3197468391  id_ID      1993    male  2012-10-02T06:40:55.524Z   \n",
       "1  3537982273  id_ID      1992    male  2012-09-29T18:03:12.111Z   \n",
       "2   823183725  en_US      1975    male  2012-10-06T03:14:07.149Z   \n",
       "3  1872223848  en_US      1991  female  2012-11-04T08:59:43.783Z   \n",
       "4  3429017717  id_ID      1995  female  2012-09-10T16:06:53.132Z   \n",
       "\n",
       "             location  timezone  \n",
       "0    Medan  Indonesia     480.0  \n",
       "1    Medan  Indonesia     420.0  \n",
       "2  Stratford  Ontario    -240.0  \n",
       "3        Tehran  Iran     210.0  \n",
       "4                 NaN     420.0  "
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#读取数据\n",
    "df = pd.read_csv(\"users.csv\")\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "38209"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n_records = df.shape[0]\n",
    "n_records"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.core.frame.DataFrame'>\n",
      "RangeIndex: 38209 entries, 0 to 38208\n",
      "Data columns (total 7 columns):\n",
      "user_id      38209 non-null int64\n",
      "locale       38209 non-null object\n",
      "birthyear    38209 non-null object\n",
      "gender       38100 non-null object\n",
      "joinedAt     38152 non-null object\n",
      "location     32745 non-null object\n",
      "timezone     37773 non-null float64\n",
      "dtypes: float64(1), int64(1), object(5)\n",
      "memory usage: 2.0+ MB\n"
     ]
    }
   ],
   "source": [
    "df.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "locale属性的不同取值和出现的次数\n",
      "\n",
      "en_US    17073\n",
      "id_ID    11817\n",
      "es_LA     1999\n",
      "en_GB     1745\n",
      "es_ES      981\n",
      "fa_IR      676\n",
      "ar_AR      584\n",
      "hu_HU      544\n",
      "fr_FR      529\n",
      "pt_BR      472\n",
      "ka_GE      407\n",
      "zh_CN      183\n",
      "ru_RU      135\n",
      "ja_JP      121\n",
      "de_DE      119\n",
      "tr_TR      109\n",
      "ko_KR       91\n",
      "it_IT       78\n",
      "vi_VN       61\n",
      "fr_CA       49\n",
      "zh_TW       41\n",
      "pt_PT       36\n",
      "th_TH       27\n",
      "km_KH       25\n",
      "pl_PL       24\n",
      "jv_ID       23\n",
      "sv_SE       22\n",
      "cs_CZ       22\n",
      "el_GR       19\n",
      "zh_HK       19\n",
      "         ...  \n",
      "bg_BG       11\n",
      "hr_HR       11\n",
      "nl_NL       10\n",
      "he_IL        9\n",
      "sk_SK        7\n",
      "sr_RS        6\n",
      "en_IN        5\n",
      "bn_IN        4\n",
      "nb_NO        4\n",
      "ca_ES        4\n",
      "fi_FI        4\n",
      "mk_MK        4\n",
      "da_DK        4\n",
      "bs_BA        3\n",
      "mn_MN        3\n",
      "lt_LT        2\n",
      "uk_UA        2\n",
      "ku_TR        2\n",
      "lv_LV        2\n",
      "fb_LT        2\n",
      "en_UD        2\n",
      "af_ZA        2\n",
      "az_AZ        2\n",
      "hi_IN        1\n",
      "cy_GB        1\n",
      "pa_IN        1\n",
      "eo_EO        1\n",
      "et_EE        1\n",
      "tl_PH        1\n",
      "es_MX        1\n",
      "Name: locale, Length: 64, dtype: int64\n",
      "\n",
      "location属性的不同取值和出现的次数\n",
      "\n",
      "Medan  Indonesia                       4509\n",
      "Yogyakarta                             3092\n",
      "Phnom Penh                             2169\n",
      "Los Angeles  California                1555\n",
      "                                       1475\n",
      "Santo Domingo  Dominican Republic      1442\n",
      "Toronto  Ontario                        696\n",
      "Phnom Penh  11                          631\n",
      "Tbilisi  Georgia                        540\n",
      "Phnom Pen  Phnum Penh  Cambodia         471\n",
      "San Francisco  California               434\n",
      "Jogjakarta  Indonesia                   418\n",
      "Djokja  Yogyakarta  Indonesia           398\n",
      "Jakarta  Indonesia                      394\n",
      "Jakarta  04                             293\n",
      "Los Angeles  CA                         220\n",
      "Bekasi                                  211\n",
      "Medan  26                               211\n",
      "Torrance  CA                            193\n",
      "undefined  undefined                    191\n",
      "Bandung  Indonesia                      179\n",
      "Miskolc  Hungary                        173\n",
      "Porto Alegre                            159\n",
      "Santo Domingo  05                       155\n",
      "Purwokerto  Jawa Tengah  Indonesia      154\n",
      "Surabaya  Indonesia                     154\n",
      "Ottawa  Ontario                         149\n",
      "New York  New York                      140\n",
      "Jombang  Jawa Timur  Indonesia          131\n",
      "Phoenix  Arizona                        128\n",
      "                                       ... \n",
      "Black Lake  Quebec                        1\n",
      "Edinburgh  United Kingdom                 1\n",
      "Niederglatt  Zurich                       1\n",
      "Fairfield  Connecticut                    1\n",
      "Pabbi  Khyber Pakhtunkhwa  Pakistan       1\n",
      "Wellington  New Zealand                   1\n",
      "El Segundo  California                    1\n",
      "Tegucigalpa  08                           1\n",
      "Tallahassee  Florida                      1\n",
      "Port-of-spain  05                         1\n",
      "Covina  CA                                1\n",
      "Agen  97                                  1\n",
      "Hawthorne  CA                             1\n",
      "Columbus  Ohio                            1\n",
      "Roseville  California                     1\n",
      "Keelung  Taiwan                           1\n",
      "Alastua  Jakarta Raya  Indonesia          1\n",
      "Aberdeen  SD                              1\n",
      "Hamedan  Hamadan  Iran                    1\n",
      "Redding  CA                               1\n",
      "Dumay  Sud  Haiti                         1\n",
      "Baulkham Hills  02                        1\n",
      "El Tigre  Venezuela                       1\n",
      "Binjai  26                                1\n",
      "North Highlands  California               1\n",
      "Dana Point  California                    1\n",
      "San Antonio  TX                           1\n",
      "Evanston  Illinois                        1\n",
      "Hermiston  Oregon                         1\n",
      "Ksour Essef                               1\n",
      "Name: location, Length: 2804, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "#查看类别型变量的取值情况\n",
    "var = ['locale','location']\n",
    "for v in var:\n",
    "    print '\\n%s属性的不同取值和出现的次数\\n'%v\n",
    "    print df[v].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#user_id不作为聚类属性\n",
    "df = df.drop([\"user_id\"], axis=1)\n",
    "        \n",
    "#location有缺失值，粗暴抛弃\n",
    "#也可以将缺失值作为另外一类：others\n",
    "#df = df.drop([\"location\"], axis=1)\n",
    "#df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#特征编码\n",
    "import datetime\n",
    "import hashlib\n",
    "import locale\n",
    "\n",
    "from collections import defaultdict\n",
    "from sklearn.preprocessing import normalize\n",
    "\n",
    "class FeatureEng:\n",
    "    def __init__(self):\n",
    "         # 载入 locales\n",
    "        self.localeIdMap = defaultdict(int)\n",
    "        for i, l in enumerate(locale.locale_alias.keys()):\n",
    "          self.localeIdMap[l] = i + 1\n",
    "        \n",
    "        # 载入 gender id 字典\n",
    "        ##缺失补0\n",
    "        self.genderIdMap = defaultdict(int, {'NaN': 0, \"male\":1, \"female\":2})\n",
    "\n",
    "  \n",
    "    def getLocaleId(self, locstr):\n",
    "        return self.localeIdMap[locstr.lower()]\n",
    "\n",
    "    def getGenderId(self, genderStr):\n",
    "        return self.genderIdMap[genderStr]\n",
    "\n",
    "    def getJoinedYearMonth(self, dateString):\n",
    "        try:\n",
    "            dttm = datetime.datetime.strptime(dateString, \"%Y-%m-%dT%H:%M:%S.%fZ\")\n",
    "            #return \"\".join([str(dttm.year), str(dttm.month)])\n",
    "            return (dttm.year-2010)*12 + dttm.month\n",
    "        except:  #缺失补0\n",
    "          return 0\n",
    "    def getCountryId(self, location):\n",
    "        if (isinstance(location, str)\n",
    "            and len(location.strip()) > 0\n",
    "            and location.rfind(\"  \") > -1):\n",
    "            return self.countryIdMap[location[location.rindex(\"  \") + 2:].lower()]\n",
    "        else:\n",
    "            return 0\n",
    "\n",
    "    def getBirthYearInt(self, birthYear):\n",
    "        #缺失补0\n",
    "        try:\n",
    "          return 0 if birthYear == \"None\" else int(birthYear)\n",
    "        except:\n",
    "          return 0\n",
    "\n",
    "    def getTimezoneInt(self, timezone):\n",
    "        try:\n",
    "          return int(timezone)\n",
    "        except:  #缺失值处理\n",
    "          return 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "FE = FeatureEng()\n",
    "\n",
    "cols = ['LocaleId', 'BirthYearInt', 'GenderId', 'JoinedYearMonth', 'locationId', 'TimezoneInt']\n",
    "n_cols = len(cols)\n",
    "userMatrix = np.zeros((df.shape[0],n_cols), dtype=np.int)\n",
    "\n",
    "for i in range(df.shape[0]): \n",
    "    userMatrix[i, 0] = FE.getLocaleId(df.loc[i,'locale'])\n",
    "    userMatrix[i, 1] = FE.getBirthYearInt(df.loc[i,'birthyear'])\n",
    "    userMatrix[i, 2] = FE.getGenderId(df.loc[i,'gender'])\n",
    "    userMatrix[i, 3] = FE.getJoinedYearMonth(df.loc[i,'joinedAt'])\n",
    "    userMatrix[i, 4] = FE.getCountryId(df['location'])\n",
    "    userMatrix[i, 5] = FE.getTimezoneInt(df.loc[i,'timezone'])\n",
    "\n",
    "\n",
    "df_FE = pd.DataFrame(data=userMatrix, columns=cols)  \n",
    "#mmwrite(\"US_userMatrix\", userMatrix)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style>\n",
       "    .dataframe thead tr:only-child th {\n",
       "        text-align: right;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: left;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>LocaleId</th>\n",
       "      <th>BirthYearInt</th>\n",
       "      <th>GenderId</th>\n",
       "      <th>JoinedYearMonth</th>\n",
       "      <th>locationId</th>\n",
       "      <th>TimezoneInt</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>230</td>\n",
       "      <td>1993</td>\n",
       "      <td>1</td>\n",
       "      <td>34</td>\n",
       "      <td>0</td>\n",
       "      <td>480</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>230</td>\n",
       "      <td>1992</td>\n",
       "      <td>1</td>\n",
       "      <td>33</td>\n",
       "      <td>0</td>\n",
       "      <td>420</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>320</td>\n",
       "      <td>1975</td>\n",
       "      <td>1</td>\n",
       "      <td>34</td>\n",
       "      <td>0</td>\n",
       "      <td>-240</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>320</td>\n",
       "      <td>1991</td>\n",
       "      <td>2</td>\n",
       "      <td>35</td>\n",
       "      <td>0</td>\n",
       "      <td>210</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>230</td>\n",
       "      <td>1995</td>\n",
       "      <td>2</td>\n",
       "      <td>33</td>\n",
       "      <td>0</td>\n",
       "      <td>420</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   LocaleId  BirthYearInt  GenderId  JoinedYearMonth  locationId  TimezoneInt\n",
       "0       230          1993         1               34           0          480\n",
       "1       230          1992         1               33           0          420\n",
       "2       320          1975         1               34           0         -240\n",
       "3       320          1991         2               35           0          210\n",
       "4       230          1995         2               33           0          420"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_FE.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 归一化用户矩阵\n",
    "userMatrix = normalize(userMatrix, norm=\"l2\", axis=0, copy=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 一个参数点（聚类数据为K）的模型，在校验集上评价聚类算法性能\n",
    "from sklearn.cluster import MiniBatchKMeans\n",
    "def K_cluster_analysis(K, X_train):\n",
    "    start = time.time()\n",
    "    \n",
    "    print(\"K-means begin with clusters: {}\".format(K));\n",
    "    \n",
    "    #K-means,在训练集上训练\n",
    "    mb_kmeans = MiniBatchKMeans(n_clusters = K)\n",
    "    mb_kmeans.fit(X_train)\n",
    "    \n",
    "    #保存预测结果\n",
    "    col_name =\"cluster_\" + str(K)\n",
    "    X_train[col_name] = mb_kmeans.predict(X_train)\n",
    "\n",
    "    # K值的评估标准\n",
    "    #常见的方法有轮廓系数Silhouette Coefficient和Calinski-Harabasz Index\n",
    "    #这两个分数值越大则聚类效果越好\n",
    "    CH_score = metrics.calinski_harabaz_score(X_train, X_train[col_name])\n",
    "    #用这个做判别的时候会报内存错误\n",
    "    #CH_score = metrics.silhouette_score(X_train, X_train[col_name])\n",
    "    \n",
    "    end = time.time()\n",
    "    print(\"CH_score: {}, time elaps:{}\".format(CH_score, int(end-start)))\n",
    "    \n",
    "    return  CH_score    #保存预测结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "K-means begin with clusters: 20\n",
      "CH_score: 162165.616685, time elaps:0\n",
      "K-means begin with clusters: 40\n",
      "CH_score: 107268.879076, time elaps:0\n",
      "K-means begin with clusters: 80\n",
      "CH_score: 131997.262769, time elaps:0\n"
     ]
    }
   ],
   "source": [
    "# 设置超参数（聚类数目K）搜索范围\n",
    "Ks = [20, 40, 80]\n",
    "CH_scores = []\n",
    "for K in Ks:\n",
    "    ch = K_cluster_analysis(K, df_FE)\n",
    "    CH_scores.append(ch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#保存聚类结果\n",
    "df_FE.to_csv('users_FE.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0xbc13978>]"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\ndhUNM+sDbACeKAwV0cxLEd/WexWVR2czyzOzvGXLlm07aSkze+4JV10FY8eGPQykcpg5Ezp1CvMr\n+veHww6DV14J8Ysvhp13jjtDKU+lLhpm1hE4DTjf3Qv/My8AGiY0ywK+2kZ8OVDTzKptEd/svaLj\ne7DFbbJC7j7C3XPcPadevXqlPSUpheuug113hYED485EytOmTfDCC/DHP4YZ2uPGwUUXwdy58OKL\nYSa3bh5XDqUqGmbWBugJnOHuiXu6TQQ6RCOfGgPZwPvAB0B2NFKqOqGzfGJUbF4D2kev7wg8n/Be\nHaPH7YFXE4qTpIg6dcJomAkTYPr0uLORsvb996FD+ze/CWs/zZsXJnYuXgz33x/iUrkkM+R2LPAO\n0MzMCsysE3AvsBswxcymm9kDAO4+GxgPzAEmAVe6+8aoT6ILMBmYC4yP2kIoPj3MLJ/QZzEyio8E\n6kTxHsD/hulKauneHWrWDLcqJDN88QVcf33YFa9LF6hVK9yGXLAgrDpbu3bcGUpcLNP+eM/JyfG8\nvLy406h0bropDMN991048si4s5HScA/fv2HD4JlnQqx9+zDfolWreHOT8mdm09w9p7h2mhEuZeLq\nq6FuXV1tpKOffw5XEa1ahR3wpkyBa64JVxXjxqlgyOZUNKRM7LpruG3xyithnL6kvhUrQv9E48Zh\n9eJVq0L/xeLFYU/4ffeNO0NJRSoaUmauuAL22uuXtakkNX3ySZiYmZUFvXuHJe9feCGMhLriivAH\ngMjWqGhImdlllzAT+I034NVX485GErmHq8C2bUOReOSRcHUxc2a4HfWnP2lXPEmOfkykTHXuHEbc\n9O2rq41U8OOPMGIEHHIInHJKGBadmxtuQT38cIiLlISKhpSpHXcMBePdd+Hll4tvL+Xjq6/CaLaG\nDeHSS8P35dFH4fPPw/dHc2CltFQ0pMxdeGHoXFXfRsXLy4O//jUsX3/zzXDsseF24bRp8Le/aVc8\n2X4qGlLmdtghbAv74YdhVzYpXxs2hL0qfv97OOIImDgxTMjLzw/zLY49Vkt8SNlR0ZBycf75cMAB\nYd7Gpk1xZ5OZVq+Gf/wj7F3Rvn24JXXnnWFXvGHDoEmTuDOUTKSiIeWiWrWwZPqsWfDPf8adTWbJ\nzw+TKbOy4Nprw9Lkzz4bdsXr2hV23z3uDCWTaRkRKTebNsGhh4YZx7Nmade27eEOr78eriT+9a/w\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0xe693278>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 绘制不同维数下模型的性能，找到最佳模型／参数（分数最高）\n",
    "plt.plot(Ks, np.array(CH_scores), 'b-')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "可以看到最高得分在类别数为20时，看老师采用silhouette_score评估标准时，性能最好的是80，由于机器内存不足，采用silhouette_score会报内存错误，为啥会结果不一样？"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
